package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.controller.GoodsController
import com.zxy.supplier_system.server.controller.ProcurementReceiptController
import com.zxy.supplier_system.server.controller.ProcurementReceiptsItemController
import com.zxy.supplier_system.server.entity.*
import com.zxy.supplier_system.server.repository.*
import com.zxy.supplier_system.server.utils.orElse404
import com.zxy.supplier_system.server.utils.toIntIdAndName
import com.zxy.supplier_system.server.utils.toLongIdAndName
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.math.BigDecimal
import java.time.OffsetDateTime

@Service
class ProcurementReceiptService(
    private val procurementReceiptRepository: ProcurementReceiptRepository,
    private val goodsRepository: GoodsRepository,
    private val systemServiceService: SystemServiceService,
    private val procurementReceiptItemRepository: ProcurementReceiptItemRepository,
    private val employeeOperateRecordService: EmployeeOperateRecordService,
    private val goodsUnitRepository: GoodsUnitRepository,
    private val procurementService: ProcurementService,
) {
    @Transactional(readOnly = true)
    fun queryProcurementReceipts(
        request: ProcurementReceiptController.QueryProcurementReceiptRequest,
        pageable: Pageable
    ): Page<ProcurementReceiptController.QueryProcurementReceiptResponse> {
        return this.procurementReceiptRepository.findAll(
            Specification.allOf(
                ProcurementReceiptRepository.Specifications.createdDateTimeGreaterThanOrEqualTo(request.startCreatedDate),
                ProcurementReceiptRepository.Specifications.createdDateTimeLessThanOrEqualTo(request.endCreatedDate),
                ProcurementReceiptRepository.Specifications.validatedDateTimeGreaterThanOrEqualTo(request.validatedStartDate),
                ProcurementReceiptRepository.Specifications.validatedDateTimeLessThanOrEqualTo(request.validatedEndDate),
                ProcurementReceiptRepository.Specifications.procurementSupplierIn(request.supplierIds),
            ), pageable
        ).map {
            ProcurementReceiptController.QueryProcurementReceiptResponse(
                it.id,
                it.validatedDateTime,
                it.createdDateTime,
                it.supplier.toIntIdAndName(),
                it.items.size,
                it.procurement!!.createdDateTime
            )
        }
    }

    @Transactional(readOnly = true)
    fun getProcurementReceiptDetail(
        id: Int,
        includeItems: Boolean,
    ): ProcurementReceiptController.ProcurementReceiptDetail {
        val systemService = systemServiceService.getCurrentSystemService()
        val procurementReceipt =
            this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(id, systemService)
                .orElse404()
        return ProcurementReceiptController.ProcurementReceiptDetail(
            procurementReceipt.id,
            procurementReceipt.createdDateTime,
            procurementReceipt.supplier.toIntIdAndName(),
            if (includeItems)
                procurementReceipt.items.map { item ->
                    ProcurementReceiptController.ProcurementReceiptItemResponse(
                        item.goods.id,
                        item.goods.name,
                        item.goods.barcode,
                        item.goods.price,
                        item.goods.amount,
                        item.goods.brand?.toIntIdAndName(),
                        item.goods.category?.toIntIdAndName(),
                        item.goods.factory?.toIntIdAndName(),
                        item.amount,
                        item.goods.boxSpecification,
                        item.unit?.toLongIdAndName(),
                        item.goods.units.map {
                            QueryGoodsUnitResponse.formGoodsUnit(it)
                        },
                        if (item.subGoods == null) null else GoodsController.GoodsDetail.SubGoodsResponse.fromSubGoods(
                            item.subGoods!!
                        ),
                        item.note,
                        item.practicalAmount,
                        item.purchasePrice,
                    )
                }
            else
                emptyList(),
            procurementReceipt.validatedDateTime,
            procurementReceipt.procurement?.id
        )
    }

    @Transactional
    fun addItem(
        orderId: Int,
        addProcurementReceiptsItemRequest: ProcurementReceiptsItemController.AddProcurementReceiptsItemRequest
    ) {
        val procurementReceipt: ProcurementReceipt =
            this.procurementReceiptRepository.findByIdOrNull(orderId).orElse404()
        if (procurementReceipt.items.any { it.goods.id == addProcurementReceiptsItemRequest.goodsId && it.subGoods?.id == addProcurementReceiptsItemRequest.subGoodsId })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        procurementReceipt.items.add(ProcurementReceiptItem().apply {
            this.procurementReceipt = procurementReceipt
            this.goods = goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(
                addProcurementReceiptsItemRequest.goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()

            this.subGoods = if (addProcurementReceiptsItemRequest.subGoodsId != null)
                this.goods.subGoodsList.find { it.id == addProcurementReceiptsItemRequest.subGoodsId }.orElse404()
            else
                null
            this.practicalAmount = addProcurementReceiptsItemRequest.amount
            this.amount = addProcurementReceiptsItemRequest.amount
            this.sequence = procurementReceipt.items.nextSequence
            this.note = addProcurementReceiptsItemRequest.note
            this.unit = addProcurementReceiptsItemRequest.unitId?.let {
                goodsUnitRepository.findByIdOrNull(it)
            }
            this.purchasePrice = addProcurementReceiptsItemRequest.purchasePrice
            // 默认已验货
            this.practicalAmount = amount
        })
        this.procurementReceiptRepository.save(procurementReceipt)
        this.employeeOperateRecordService.addProcurementReceiptItem(procurementReceipt)
    }

    @Transactional
    fun deleteItem(orderId: Int, goodsId: Int) {
        val procurementReceiptItem =
            this.procurementReceiptItemRepository.findByProcurementReceipt_IdAndGoods_IdAndGoods_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        this.employeeOperateRecordService.deleteProcurementReceiptItem(procurementReceiptItem)
        return this.procurementReceiptItemRepository.delete(procurementReceiptItem)
    }

    @Transactional
    fun deleteItem(orderId: Int, goodsId: Int, subGoodsId: Int) {
        val procurementItem =
            this.procurementReceiptItemRepository.findByProcurementReceipt_IdAndGoods_IdAndSubGoods_IdAndProcurementReceipt_CreatedBy_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService()
            )
        if (procurementItem != null) {
            this.procurementReceiptItemRepository.delete(procurementItem)
            this.employeeOperateRecordService.deleteProcurementReceiptItem(procurementItem)
        }
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        orderId: Int,
        updateItemAmountRequest: ProcurementReceiptsItemController.UpdateItemAmountRequest
    ) {
        val procurementReceiptItem =
            this.procurementReceiptItemRepository.findByProcurementReceipt_IdAndGoods_IdAndGoods_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementReceiptItem.amount = updateItemAmountRequest.amount
        procurementReceiptItem.unit = updateItemAmountRequest.unitId?.let {
            this.goodsUnitRepository.findByIdOrNull(it)
        }
        this.procurementReceiptItemRepository.save(procurementReceiptItem)
        this.employeeOperateRecordService.updateProcurementReceiptItem(procurementReceiptItem)
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        orderId: Int,
        subGoodsId: Int,
        updateItemAmountRequest: ProcurementReceiptsItemController.UpdateItemAmountRequest
    ) {
        val procurementItem =
            this.procurementReceiptItemRepository.findByProcurementReceipt_IdAndGoods_IdAndSubGoods_IdAndProcurementReceipt_CreatedBy_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementItem.amount = updateItemAmountRequest.amount
        procurementItem.unit = updateItemAmountRequest.unitId?.let {
            this.goodsUnitRepository.findByIdOrNull(it)
        }
        this.procurementReceiptItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementReceiptItem(procurementItem)
    }

    fun queryOrderItemsInOrder(
        orderId: Int,
        pageable: Pageable
    ): Page<ProcurementReceiptsItemController.OrderItemInOrderSummary> {
        return this.procurementReceiptItemRepository.findAllByProcurementReceipt_IdOrderBySequence(orderId, pageable)
            .map {
                val goods = it.goods
                ProcurementReceiptsItemController.OrderItemInOrderSummary.fromGoodsAndAmount(
                    goods,
                    it.amount,
                    it.unit,
                    it.subGoods,
                    it.practicalAmount
                )
            }
    }

    fun exists(orderId: Int, goodsId: Int): Boolean {
        return this.procurementReceiptItemRepository.existsByProcurementReceipt_IdAndGoods_Id(orderId, goodsId)
    }

    fun exists(orderId: Int, barcode: String): Boolean {
        return this.procurementReceiptItemRepository.existsByProcurementReceipt_IdAndGoods_Barcode(orderId, barcode)
    }

    @Transactional
    fun addItem(orderId: Int, barcode: String): ProcurementReceiptsItemController.OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurementReceipt = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        val goods =
            goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(barcode, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(procurementReceipt, goods)
    }

    @Transactional
    fun addItem(orderId: Int, goodsId: Int): ProcurementReceiptsItemController.OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurementReceipt = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        val goods =
            goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(goodsId, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(procurementReceipt, goods)
    }

    private fun orderItemInOrderSummary(
        procurementReceipt: ProcurementReceipt,
        goods: Goods,
        subGoods: SubGoods? = null,
    ): ProcurementReceiptsItemController.OrderItemInOrderSummary {
        val amount = 1
        val goodsUnit = goods.basicUnit
        procurementReceipt.items.add(ProcurementReceiptItem().apply {
            this.procurementReceipt = procurementReceipt
            this.goods =
                goods
            this.amount = amount
            this.sequence = procurementReceipt.items.nextSequence
            this.unit = goodsUnit
            this.subGoods = subGoods
            this.purchasePrice = getLatestItemPurchaseByProcurementAndItemKey(procurementReceipt,goods,subGoods)?: BigDecimal.ZERO
        })
        this.procurementReceiptRepository.save(procurementReceipt)
        this.employeeOperateRecordService.addProcurementReceiptItem(procurementReceipt)
        return ProcurementReceiptsItemController.OrderItemInOrderSummary.fromGoodsAndAmount(
            goods,
            amount,
            goodsUnit,
            subGoods,
            null
        )
    }

    @Transactional
    fun validate(id: Int, itemDefaultValidated: Boolean) {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurementReceipt = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            id,
            currentSystemService
        ).orElse404()
        if (procurementReceipt.validatedDateTime == null) {
            procurementReceipt.validatedDateTime = OffsetDateTime.now()
            procurementReceipt.items.forEach {
                if (it.practicalAmount == null && itemDefaultValidated) {
                    it.practicalAmount = it.amount
                }
                if (it.subGoods == null) {
                    it.goods.amount += it.practicalAmount ?: 0
                } else {
                    it.subGoods!!.amount += it.practicalAmount ?: 0
                }
            }
            this.procurementReceiptItemRepository.saveAll(procurementReceipt.items)
            this.procurementReceiptRepository.save(procurementReceipt)
            this.employeeOperateRecordService.validateProcurementReceipt(procurementReceipt)
        }
    }

    @Transactional
    fun updateProcurementReceiptNote(
        id: Int,
        request: ProcurementReceiptController.UpdateProcurementReceiptNoteRequest
    ) {
        val procurement = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        procurement.note = request.note
        this.procurementReceiptRepository.save(procurement)
    }

    @Transactional
    fun importFromExcelItems(
        orderId: Int,
        items: List<ProcurementReceiptsItemController.ProcurementReceiptItemExcelItem>
    ): Int {
        val procurementReceipt = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            orderId,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        val procurement = procurementReceipt.procurement!!
        val procurementItems = items.distinctBy { it.barcode + it.subGoodsNumber }
            .filterNot { item ->
                procurementReceipt.items.any {
                    it.goods.barcode == item.barcode && it.subGoods?.number == item.subGoodsNumber
                }
            }.mapIndexed { index, item ->
                ProcurementReceiptItem().apply {
                    this.sequence = procurementReceipt.items.nextSequence + index
                    val goods = goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(
                        item.barcode,
                        systemServiceService.getCurrentSystemService()
                    ).orElse404("商品条码${item.barcode}不存在")
                    this.goods = goods
                    this.subGoods = if (item.subGoodsNumber == null)
                        null
                    else
                        goods.subGoodsList.find { subGoods -> subGoods.number == item.subGoodsNumber }
                    this.amount = item.amount
                    this.practicalAmount = item.amount
                    this.unit = goods.units.find { it.name == item.unit }
                    this.note = item.note
                    this.procurementReceipt = procurementReceipt
                    this.purchasePrice =
                        procurement.items.find { it.subGoods?.number == item.subGoodsNumber && it.goods.barcode == item.barcode }?.purchasePrice
                            ?: getLatestItemPurchaseByProcurementAndItemKey(
                                procurementReceipt,
                                goods,
                                subGoods
                            )
                                    ?: goods.costPrice ?: BigDecimal.ZERO
                }

            }
        this.procurementReceiptItemRepository.saveAll(procurementItems)
        return procurementItems.size
    }

    fun getLatestItemPurchaseByProcurementAndItemKey(
        procurementReceipt: ProcurementReceipt,
        goods: Goods,
        subGoods: SubGoods? = null
    ): BigDecimal? {
        val supplier = procurementReceipt.supplier
        return getLatestItemPurchaseByProcurementAndItemKey(supplier, goods,subGoods)
    }

    fun getLatestItemPurchaseByProcurementAndItemKey(
        supplier: ProcurementSupplier,
        goods: Goods,
        subGoods: SubGoods?
    ): BigDecimal? {
        return (
                if (subGoods != null)
                    this.procurementReceiptItemRepository.findFirstByProcurementReceipt_Supplier_IdAndGoods_IdAndSubGoods_IdOrderByProcurementReceipt_CreatedDateTimeDesc(
                        supplier.id,
                        goods.id,
                        subGoods.id!!
                    )
                else
                    this.procurementReceiptItemRepository.findFirstByProcurementReceipt_Supplier_IdAndGoods_IdOrderByProcurementReceipt_CreatedDateTimeDesc(
                        supplier.id,
                        goods.id,
                    )
                )?.purchasePrice ?: this.procurementService.getLatestItemPurchaseByProcurementAndItemKey(
            supplier,
            goods,
            subGoods
        )
    }

    @Transactional
    fun validateItem(
        orderId: Int,
        goodsId: Int,
        request: ProcurementReceiptsItemController.ValidateItemRequest
    ) {
        val procurementReceiptItem =
            this.procurementReceiptItemRepository.findByProcurementReceipt_IdAndGoods_IdAndGoods_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        procurementReceiptItem.practicalAmount = request.amount
        this.employeeOperateRecordService.validateProcurementReceiptItem(procurementReceiptItem)
        this.procurementReceiptItemRepository.save(procurementReceiptItem)
    }

    @Transactional
    fun validateItem(
        orderId: Int,
        goodsId: Int,
        subGoodsId: Int,
        request: ProcurementReceiptsItemController.ValidateItemRequest
    ) {
        val procurementReceiptItem =
            this.procurementReceiptItemRepository.findByProcurementReceipt_IdAndGoods_IdAndSubGoods_IdAndProcurementReceipt_CreatedBy_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        procurementReceiptItem.practicalAmount = request.amount
        this.employeeOperateRecordService.validateProcurementReceiptItem(procurementReceiptItem)
        this.procurementReceiptItemRepository.save(procurementReceiptItem)
    }

    @Transactional
    fun delete(id: Int) {
        val procurementReceipt = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        this.employeeOperateRecordService.deleteProcurementReceipt(procurementReceipt)
        this.procurementReceiptRepository.delete(procurementReceipt)
    }

    @Transactional
    fun batchAddItems(
        orderId: Int,
        itemKeys: List<ProcurementReceiptsItemController.AddProcurementItemKey>
    ): List<ProcurementReceiptsItemController.OrderItemInOrderSummary> {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val marketOrder = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        val goodsList =
            goodsRepository.findByIdInAndSystemServiceAndDisableIsFalse(itemKeys.map { it.goodsId }, currentSystemService)
                .orElse404()
        return itemKeys.map { key ->
            val goods = goodsList.find { it.id == key.goodsId }.orElse404()
            orderItemInOrderSummary(marketOrder, goods, goods.subGoodsList.find { key.subGoodsId == it.id })
        }
    }

    @Transactional
    fun clear(orderId: Int) {
        val procurementReceipt = this.procurementReceiptRepository.findByIdAndProcurement_CreatedBy_SystemService(
            orderId,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        procurementReceipt.items.clear()
        this.procurementReceiptRepository.save(procurementReceipt)
        this.employeeOperateRecordService.clearProcurementReceipt(procurementReceipt)
    }

}
